package org.hawksee.core.data;

/**
 * This class is the base image class for monochrome operations.
 */
public abstract class BaseMonochromeImage
{
  public abstract byte getIntensity(int x, int y);
  public abstract int getHeight();
  public abstract int getWidth();
  public abstract BaseMonochromeImage getSubimage(int relativeX, int relativeY, int width, int height);
  
  private boolean gradientsCached;
  private int x;
  private int y;
  protected double[][] gradientsX;
  protected double[][] gradientsY;
  protected double[][] intensities;
  public BaseMonochromeImage()
  {
    this.x = 0;
    this.y = 0;
    this.gradientsCached = false;
  }
  /*
   * Caches all first degree image gradients for quick access later.
   */
  public void cacheGradients()
  {
    this.gradientsX = new double[this.getWidth()][this.getHeight()];
    this.gradientsY = new double[this.getWidth()][this.getHeight()];
    
    for (int x=0; x < this.getWidth(); x++) {
      for (int y=0; y < this.getHeight(); y++) {
        this.gradientsX[x][y] = this.calculateGradientX(x, y);
        this.gradientsY[x][y] = this.calculateGradientY(x, y);
      }
    }
    this.gradientsCached = true;
  }
  /*
   * Caches all intensities for quick access later.
   */
  public void cacheIntensities()
  {
    this.intensities = new double[this.getWidth()][this.getHeight()];
    
    for (int x=0; x < this.getWidth(); x++) {
      for (int y=0; y < this.getHeight(); y++) {
        this.intensities[x][y] = this.getIntensity(x, y);
      }
    }
  }
  /*
   * Calculates the x component of the pixel's gradient vector.
   */
  private double calculateGradientX(int x, int y)
  {
    double sum = 0.0;
    
    // line left
    sum -= this.getIntensity(x-1, y-1);
    sum -= 2 * this.getIntensity(x-1, y);
    sum -= this.getIntensity(x-1, y+1);
    
    // line right
    sum += this.getIntensity(x+1, y-1);
    sum += 2 * this.getIntensity(x+1, y);
    sum += this.getIntensity(x+1, y+1);
    return sum / 255.0;
  }
  /*
   * Calculates the y component of the pixel's gradient vector.
   */
  private double calculateGradientY(int x, int y)
  {
    double sum = 0.0;
    
    // line above
    sum -= this.getIntensity(x-1, y-1);
    sum -= 2 * this.getIntensity(x, y-1);
    sum -= this.getIntensity(x+1, y-1);
    
    // line below
    sum += this.getIntensity(x-1, y+1);
    sum += 2 * this.getIntensity(x, y+1);
    sum += this.getIntensity(x+1, y+1);
    return sum / 255.0;
  }
  /*
   * Gets the x gradient
   */
  public double getGradientX(int x, int y)
  {
    return this.getGradientX(x, y, false);
  }
  /*
   * Gets the x gradient
   */
  public double getGradientX(int x, int y, boolean absolute)
  {
    if (absolute) {
      x -= this.x;
      y -= this.y;
    }
    if (!this.gradientsCached) {
      this.cacheGradients();
    }
    return this.gradientsX[x][y];
  }
  /*
   * Gets the y gradient
   */
  public double getGradientY(int x, int y)
  {
    return this.getGradientY(x, y, false);
  }
  /*
   * Gets the y gradient
   */
  public double getGradientY(int x, int y, boolean absolute)
  {
    if (absolute) {
      x -= this.x;
      y -= this.y;
    }
    if (!this.gradientsCached) {
      this.cacheGradients();
    }
    return this.gradientsY[x][y];
  }
  /*
   * Returns the intensity at the absolute (x,y) position
   */
  public byte getIntensityAbsolute(int x, int y)
  {
    return this.getIntensity(x - this.x, y - this.y);
  }
  /*
   * Returns the origin position.
   */
  public Position getPosition()
  {
    return new Position(this.x, this.y);
  }
  /*
   * Returns the region that this image occupies.
   */
  public Region getRegion()
  {
    return new Region(this.x, this.y, this.getWidth(), this.getHeight());
  }
  /*
   * Gets the x position of the image origin (upper-left corner).
   */
  public int getX()
  {
    return this.x;
  }
  /*
   * Gets the y position of the image origin (upper-left corner).
   */
  public int getY()
  {
    return this.y;
  }
  /*
   * Moves the image by the specified x,y deltas.
   */
  public void moveBy(int x, int y)
  {
    this.x += x;
    this.y += y;
  }
  /*
   * Moves the image to the specified x,y positio.n
   */
  public void moveTo(int x, int y)
  {
    this.x = x;
    this.y = y;
  }
  /*
   * Returns the subimage contained in the specified region.
   */
  public BaseMonochromeImage intersection(Region region)
  {
    Region intersect = this.getRegion().intersection(region);
    if ((intersect.getWidth() > 0) && (intersect.getHeight() > 0)) {
      return this.getSubimage(intersect.getX() - this.x, intersect.getY() - this.y, 
          intersect.getWidth(), intersect.getHeight());
    } else {
      return null;
    }
  }
  
  /*
  public Image union(Image image)
  {
    if ((image.getWidth() <= 0) || (image.getHeight() <= 0)) {
      return null;
    }
    
    Region union = this.getRegion().intersection(image.getRegion());
    
    byte[][] red = new byte[union.getWidth()][union.getHeight()];
    byte[][] green = new byte[union.getWidth()][union.getHeight()];
    byte[][] blue = new byte[union.getWidth()][union.getHeight()];
    
    // initialize the image to black
    for (int x=0; x < union.getWidth(); x++) {
      for (int y=0; y < union.getHeight(); y++) {
        red[x][y] = 0;
      }
    }
    
    // first add in this image
    for (int x=0; x < this.getWidth(); x++) {
      for (int y=0; y < this.getHeight(); y++) {
        red[x + (this.x - union.left)][y + (this.y - union.top)] = this.red[x][y];
        green[x + (this.x - union.left)][y + (this.y - union.top)] = this.green[x][y];
        blue[x + (this.x - union.left)][y + (this.y - union.top)] = this.blue[x][y];
      }
    }
    // add in the second image
    for (int x=0; x < image.getWidth(); x++) {
      for (int y=0; y < image.getHeight(); y++) {
        red[x + (image.x - union.left)][y + (image.y - union.top)] = image.getRed(x, y);
        green[x + (image.x - union.left)][y + (image.y - union.top)] = image.getGreen(x, y);
        blue[x + (image.x - union.left)][y + (image.y - union.top)] = image.getBlue(x, y);
      }
    }
    return new Image(red, green, blue, width, height, union.left, union.top);
  }
  */
}